/**
 * DigitalSigner.java
 */
package us.gaaoc.framework.signature.digital;



import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;

import org.apache.log4j.Logger;

import us.gaaoc.framework.signature.digital.ldap.LdapClient;
import us.gaaoc.framework.util.io.FileUtility;



/**
 * @company: GAAOC
 * @author: hea
 * @date: Oct 19, 2005
 * 
 */
public class DigitalSigner {

	/**
	 * Properties
	 */
	private static final String PKCS12_KEYSTORE_TYPE = "PKCS12";
	private static final String X509_CERTIFICATE_TYPE = "X.509";
	private static final String CERTIFICATION_CHAIN_ENCODING = "PkiPath";
	private static final String DIGITAL_SIGNATURE_ALGORITHM_NAME = "SHA1withRSA";
	private Logger LOG = Logger.getLogger(DigitalSigner.class);

	/**
	 * Signs given local file. The certification chain and private key to be
	 * used for signing are specified by the local user who choose a PFX file
	 * and password for accessing it.
	 * 
	 * @param aFileName
	 *            the name of the file to be signed.
	 * @return the digital signature of the given file and the certification
	 *         chain of the certificate used for signing the file, both
	 *         Base64-encoded or null if the signing process is canceled by the
	 *         user.
	 * @throws DocumentSignException
	 *             when a problem arise during the singing process (e.g. invalid
	 *             file format, invalid certificate, invalid password, etc.)
	 */
	public CertificationChainAndSignatureInBase64 signFile(
			InputStream document, InputStream certificateFile, String password)
			throws DocumentSignException {

		LOG.debug("Entering signFile method!!...");
		LOG.info("Entering signFile method!!..."); /*tessssssssssssss*/
		// Load the file for signing
		byte[] documentToSign = null;
		try {
			//documentToSign = FileUtility.readFileInByteArray(document);
			documentToSign = FileUtility.read2list(document);
		} catch (IOException ioex) {
			String errorMsg = "Can not read the file for signing ";
			throw new DocumentSignException(errorMsg, ioex);
		} catch (Exception e) {
			String errorMsg = "Can not read the file for signing ";
			throw new DocumentSignException(errorMsg, e);
		}

		// Load the keystore from specified PFX file using the specified
		// password
		KeyStore userKeyStore = null;
		try {
			userKeyStore = loadKeyStoreFromPFXFile(certificateFile, password);
			LOG
					.debug("Back to the signFile method after the loading of the keystore!!...");
		} catch (Exception ex) {
			ex.printStackTrace();
           
			String errorMessage = "Can not read certificate keystore file ("
					+ ").\nThe file is either not in PKCS#12 format"
					+ " (.P12 or .PFX) or is corrupted or the password is invalid.";
		/*	LOG.error("Can not read certificate keystore file ("
					+ ").\nThe file is either not in PKCS#12 format"
					+ " (.P12 or .PFX) or is corrupted or the password is invalid."); Added by brownb*/
			throw new DocumentSignException(errorMessage, ex);
		
		}

		// Get the private key and its certification chain from the keystore
		PrivateKeyAndCertChain privateKeyAndCertChain = null;
		try {
			privateKeyAndCertChain = getPrivateKeyAndCertChain(userKeyStore,
					password);
			LOG
					.info("Private key and Certification Authority are extracted !...");
		} catch (GeneralSecurityException gsex) {
			String errorMessage = "Can not extract certification chain and "
					+ "corresponding private key from the specified keystore file "
					+ "with given password. Probably the password is incorrect.";
			throw new DocumentSignException(errorMessage, gsex);
		}

		// Check if a private key is available in the keystore
		PrivateKey privateKey = privateKeyAndCertChain.mPrivateKey;
		if (privateKey == null) {
			String errorMessage = "Can not find the private key in the "
					+ "specified file ";
			throw new DocumentSignException(errorMessage);
		}

		// Check if X.509 certification chain is available
		Certificate[] certChain = privateKeyAndCertChain.mCertificationChain;
		if (certChain == null) {
			String errorMessage = "Can not find neither certificate nor "
					+ "certification chain in the file ";
			throw new DocumentSignException(errorMessage);
		}

		// Create the result object
		CertificationChainAndSignatureInBase64 signingResult = new CertificationChainAndSignatureInBase64();

		// Save X.509 certification chain in the result encoded in Base64
		try {
			signingResult.setMCertChain(encodeX509CertChainToBase64(certChain));
		} catch (CertificateException cee) {
			String errorMessage = "Invalid certification chain found in the "
					+ "file ";
			throw new DocumentSignException(errorMessage);
		}

		// Calculate the digital signature of the file,
		// encode it in Base64 and save it in the result
		try {
			byte[] digitalSignature = signDocument(documentToSign, privateKey);
			signingResult.setMSignature(Base64Utils
					.base64Encode(digitalSignature));
		} catch (GeneralSecurityException gsex) {
			String errorMessage = "Error signing file ";
			throw new DocumentSignException(errorMessage, gsex);
		}

		// Document signing completed succesfullysign(document
		return signingResult;

	}

	/**
	 * Loads a keystore from .PFX or .P12 file (file format should be PKCS#12)
	 * using given keystore password.
	 */
	private KeyStore loadKeyStoreFromPFXFile(InputStream certificate,
			String aKeyStorePasswd) throws GeneralSecurityException,
			IOException {
		LOG.info("Loading the keystore!...");
		KeyStore keyStore = KeyStore.getInstance(PKCS12_KEYSTORE_TYPE);
		char[] password = aKeyStorePasswd.toCharArray();
		LOG
				.info("The password being passed to the loadKeyStoreFromPFXFile is ... "
						+ password.toString());
		keyStore.load(certificate, password);
		LOG.info("Keystore is loaded!!... ");
		return keyStore;
	}

	/**
	 * @return private key and certification chain corresponding to it,
	 *         extracted from given keystore using given password to access the
	 *         keystore and the same password to access the private key in it.
	 *         The keystore is considered to have only one entry that contains
	 *         both certification chain and the corresponding private key. If
	 *         the certificate has no entries, an exception is trown. It the
	 *         keystore has several entries, the first is used.
	 */
	private PrivateKeyAndCertChain getPrivateKeyAndCertChain(
			KeyStore aKeyStore, String aKeyPassword)
			throws GeneralSecurityException {
		LOG.info("Inside the getPrivateKeyAndCertChain method !!...");
		char[] password = aKeyPassword.toCharArray();
		Enumeration aliasesEnum = aKeyStore.aliases();
		if (aliasesEnum.hasMoreElements()) {
			String alias = (String) aliasesEnum.nextElement();
			Certificate[] certificationChain = aKeyStore
					.getCertificateChain(alias);
			PrivateKey privateKey = (PrivateKey) aKeyStore.getKey(alias,
					password);
			PrivateKeyAndCertChain result = new PrivateKeyAndCertChain();
			result.mPrivateKey = privateKey;
			/*******************************************************************
			 * Security Hazard to put private key info in log files
			 * 
			 * Only use the following debug statement when debugging locally and
			 * deleting log files after end of debugging.
			 * *****************************************************************
			 */
			// LOG.debug("private key is..." + result.mPrivateKey.toString());
			result.mCertificationChain = certificationChain;
			LOG.info("certification chain is..."
					+ result.mCertificationChain.toString());
			return result;
		} else {
			throw new KeyStoreException("The keystore is empty!");
		}
	}

	/**
	 * @return Base64-encoded ASN.1 DER representation of given X.509
	 *         certification chain.
	 */
	private String encodeX509CertChainToBase64(Certificate[] aCertificationChain)
			throws CertificateException {
		List certList = Arrays.asList(aCertificationChain);
		CertificateFactory certFactory = CertificateFactory
				.getInstance(X509_CERTIFICATE_TYPE);
		CertPath certPath = certFactory.generateCertPath(certList);
		byte[] certPathEncoded = certPath
				.getEncoded(CERTIFICATION_CHAIN_ENCODING);
		String base64encodedCertChain = Base64Utils
				.base64Encode(certPathEncoded);
		return base64encodedCertChain;
	}

	/**
	 * Signs given document with a given private key.
	 */
	private byte[] signDocument(byte[] aDocument, PrivateKey aPrivateKey)
			throws GeneralSecurityException {
		Signature signatureAlgorithm = Signature
				.getInstance(DIGITAL_SIGNATURE_ALGORITHM_NAME);
		signatureAlgorithm.initSign(aPrivateKey);
		signatureAlgorithm.update(aDocument);
		byte[] digitalSignature = signatureAlgorithm.sign();
		return digitalSignature;
	}

	/**
	 * Data structure that holds a pair of private key and certification chain
	 * corresponding to this private key.
	 */
	static class PrivateKeyAndCertChain {
		public PrivateKey mPrivateKey;

		public Certificate[] mCertificationChain;
	}

	public byte[] sign(ByteArrayInputStream pdfDoc, String cnName,
			String password) {
		LOG.info("Invoking ther sign method for the " + cnName);
		byte[] signatureBytes = null;
		String signatureValue = null;
		try {
			/* for LDAP */

			LdapClient ldapClient = new LdapClient();
			InputStream certFile = ldapClient.retreiveUserCertificate(cnName);
			// FileInputStream certFile =
			// localClient.retreiveUserCertificate(new
			// File("/home/tapshals/test/backUpOfDigSig.p12"));

			CertificationChainAndSignatureInBase64 ccasib64 = this.signFile(
					pdfDoc, certFile, password);
			LOG.info("Pdf file is signed!!...");
			LOG.debug("Pdf file is signed!!...");
			signatureValue = ccasib64.getMSignature();
			String certChain1 = ccasib64.getMCertChain();
			byte[] certChainArray = certChain1.getBytes();

			// to decode signature from base64 to original value.
			// signatureBytes = Base64Utils.base64Decode(signatureValue);
			// save this value in SignatureAndCertChain Objec
			SignatureAndCertChain.signature = signatureValue.getBytes();

			// save this value in SignatureAndCertChain Object
			SignatureAndCertChain.certificationChain = certChainArray;

		} catch (Exception e) {			
			e.printStackTrace();//TODO: We should throw this in a runtime exception!
		}
		// return signatureBytes;
		if (signatureValue == null )
		{
			LOG.error(String.format("Can't find electronic image for %s ",cnName + " or check certificate password."));
			/*-----------------*/
			throw new NullPointerException(String.format("Can't find digital signature for %s",cnName));
		}
		return signatureValue.getBytes();
	}
}
